﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Slock.Components;
using Slock.GameObjects.ActorComponents;
using Slock.Utility;

namespace Slock.GameObjects
{
    /// <summary>
    /// The lowest level game object, an Actor is anything in a level that can exist in the world
    /// </summary>
    public abstract class Actor
    {
        #region Globals

        private static int INCREMENTAL_ID = 0;

        #endregion

        #region Fields

        private int id = (INCREMENTAL_ID++);
        private bool isEnabled;
        protected Vector3 position;
        protected Rotator rotation;
        private World world;
        private Dictionary<string, ActorComponent> components;

        #endregion

        /// <summary>
        /// Initializes a new instance of Actor
        /// </summary>
        /// <param name="world">The world this actor exists in</param>
        public Actor(World world)
        {
            this.world = world;
            this.components = new Dictionary<string, ActorComponent>(1);
            this.isEnabled = true;
        }

        #region Methods

        /// <summary>
        /// Allows an actor to reset it's state
        /// <remarks>Default implementation zeroes out position and rotation</remarks>
        /// </summary>
        public virtual void Reset()
        { 
            position = Vector3.Zero;
            rotation = Rotator.Zero;
        }

        /// <summary>
        /// Called by the engine to update the actor and it's components
        /// </summary>
        /// <param name="delta">elapsed time in second since last update</param>
        public void Update(float delta)
        {
            if (isEnabled)
            {
                Tick(delta);
                foreach (ActorComponent component in components.Values)
                    if (component.Enabled)
                        component.Tick(delta);
            }
        }

        /// <summary>
        /// Override to implement your own ticking logic
        /// </summary>
        /// <param name="delta">elapsed time in seconds since last update</param>
        protected virtual void Tick(float delta)
        { }

        /// <summary>
        /// Add a component to this actor, will overwrite any component added of the same type
        /// </summary>
        /// <param name="component">The component to add</param>
        protected void AddComponent(string name, ActorComponent component)
        { this.components[name] = component; }

        /// <summary>
        /// Does an equality check against the object passed in
        /// </summary>
        /// <param name="obj">the object to compare</param>
        /// <returns>Returns true if the object is an actor and has the same hash code</returns>
        public override bool Equals(object obj)
        { var actor = (obj as Actor); return actor != null && actor.GetHashCode() == this.GetHashCode(); }

        /// <summary>
        /// Gets a hash code for this actor
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        { return base.GetHashCode() ^ id; }

        #endregion

        #region Properties

        /// <summary>
        /// Get this actor's unique ID
        /// </summary>
        public int ID
        { get { return id; } }

        /// <summary>
        /// Get the world this actor belongs to
        /// </summary>
        public World World
        { get { return world; } }

        /// <summary>
        /// Get or set if this actor should be ticked
        /// </summary>
        public bool bIsEnabled
        {
            get { return this.isEnabled; }
            set { this.isEnabled = value; }
        }

        /// <summary>
        /// Gets an actor component from this actor
        /// </summary>
        /// <param name="type">the type of the actor you would like to retrieve</param>
        /// <returns>The actor component in this actor, if it exists. Otherwise it returns null</returns>
        public ActorComponent this[string type]
        { 
            get 
            {
                if (this.components.ContainsKey(type))
                    return this.components[type];
                else return null;
            }
        }

        /// <summary>
        /// Get the position of this actor
        /// </summary>
        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        /// <summary>
        /// Get or set the rotation of this actor
        /// </summary>
        public Rotator Rotation
        {
            get { return this.rotation; }
            set { this.rotation = value; }
        }

        public ActorComponent[] Components
        { get { return this.components.Values.ToArray(); } }

        #endregion
    }

    /// <summary>
    /// Various physics states an Actor can be in
    /// </summary>
    [Flags]
    public enum PhysState
    {
        /// <summary>
        /// Means that this actor is sleeping or not moving
        /// </summary>
        Idle = 0 << 1,

        /// <summary>
        /// If this actor is walking
        /// </summary>
        Walking = 1 << 2,

        /// <summary>
        /// If this actor is falling due to gravity
        /// </summary>
        Falling = 1 << 3,

        /// <summary>
        /// This actor has full dynamic physics
        /// </summary>
        RigidBody = 1 << 4
    }
}
